use std::net::SocketAddr;
use std::path::{Path, PathBuf};

use crate::{Error, Result};

/// 配置选项
#[derive(Clone)]
pub struct Configure {
    /// mqtt broker地址.
    pub broker_uri: String,
    /// carla安装目录.
    pub carla_path: PathBuf,
    /// carla连接地址.
    pub carla_addr: SocketAddr,
    /// carla连接超时设置.
    pub carla_timeout_ms: u32,
    /// zeromq绑定地址.
    pub client_proxy_uri: String,
    /// 提供给客户端的zeromq连接地址.
    pub client_proxy_extern_uri: String,
    /// scenario_runner安装目录.
    pub scenario_runner_path: PathBuf,
    /// 车辆传感器配置目录.
    pub sensors_cfg_path: PathBuf,
    /// simfile目录.
    pub carsim_cfg_path: PathBuf,
    /// 下载地图文件保存路径.
    pub tmp_map_path: PathBuf,
    /// 下载场景文件保存路径.
    pub tmp_openscenario_path: PathBuf,
    /// 下载场景配置文件保存路径.
    pub tmp_scenario_cfg_path: PathBuf,
    /// 下载场景文件压缩包解压目录.
    pub tmp_scenario_package_dir: PathBuf,
    /// 命令id去重.
    pub command_id_filter: bool,
    /// 状态信息发布周期.
    pub publish_status_period_ms: u32,
}

impl Configure {
    /// 读取指定路径的配置文件.
    ///
    /// root为节点安装目录.
    /// 某些配置项存在默认值,比如carla安装目录默认为root上一级目录.
    pub fn load<P: AsRef<Path>>(root: P, path: P) -> Result<Self> {
        let config = config::Config::builder()
            .add_source(config::File::from(path.as_ref()))
            .build()
            .map_err(Error::from)?;

        let broker_uri = config
            .get_string("broker_uri")
            .map_or("tcp://127.0.0.1:1883".to_string(), |v| v);

        let carla_path = if let Ok(s) = config.get_string("carla_path") {
            PathBuf::from(s)
        } else if let Some(p) = std::env::current_dir().map_err(Error::from)?.parent() {
            p.to_path_buf()
        } else {
            return Err(Error::new("carla_path error".to_string()));
        };

        let carla_addr = config
            .get_string("carla_addr")
            .map_or("127.0.0.1:2000".to_string(), |v| v)
            .parse()
            .map_err(Error::from)?;

        let carla_timeout_ms = config.get_int("carla_timeout_ms").map_or(5000, |v| v) as u32;

        let client_proxy_uri = config
            .get_string("client_proxy_uri")
            .map_or("0.0.0.0:17000".to_string(), |v| v);

        let client_proxy_extern_uri = config
            .get_string("client_proxy_extern_uri")
            .map_or("127.0.0.1:17000".to_string(), |v| v);

        let scenario_runner_path = if let Ok(s) = config.get_string("scenario_runner_path") {
            PathBuf::from(s)
        } else if let Some(p) = std::env::current_dir().map_err(Error::from)?.parent() {
            p.join("scenario_runner")
        } else {
            return Err(Error::new("scenario_runner_path error".to_string()));
        };

        let sensors_cfg_path = if let Ok(s) = config.get_string("sensors_cfg_path") {
            PathBuf::from(s)
        } else if let Some(p) = std::env::current_dir().map_err(Error::from)?.parent() {
            p.join("sensors_cfg")
        } else {
            return Err(Error::new("sensors_cfg_path error".to_string()));
        };

        let carsim_cfg_path = if let Ok(s) = config.get_string("carsim_cfg_path") {
            PathBuf::from(s)
        } else if let Some(p) = std::env::current_dir().map_err(Error::from)?.parent() {
            p.join("carsim_cfg")
        } else {
            return Err(Error::new("carsim_cfg_path error".to_string()));
        };

        let tmp_dir = root
            .as_ref()
            .to_owned()
            .join("var")
            .join("tmp")
            .join("sim_server");

        let command_id_filter = config.get_bool("command_id_filter").map_or(false, |v| v);

        let publish_status_period_ms = config
            .get_int("publish_status_period_ms")
            .map_or(1000, |v| v) as u32;

        Ok(Self {
            broker_uri,
            carla_path,
            carla_addr,
            carla_timeout_ms,
            client_proxy_uri,
            client_proxy_extern_uri,
            scenario_runner_path,
            sensors_cfg_path,
            carsim_cfg_path,
            tmp_map_path: tmp_dir.join("map.xodr"),
            tmp_openscenario_path: tmp_dir.join("scenario.xosc"),
            tmp_scenario_cfg_path: tmp_dir.join("scenario.xml"),
            tmp_scenario_package_dir: tmp_dir.join("scenario"),
            command_id_filter,
            publish_status_period_ms,
        })
    }
}
